GO
1. Shell脚本的条件测试
1.1. 条件测试方法综述
通常,在bash的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。
执行条件测试表达式后通常会返回真
或假
,就像执行命令后的返回值为0表示真,非0表示假
一样。
在bash编程里,条件测试常用的语法形式见下表:
条件测试语法 | 说明 |
---|---|
语法1:test <测试表达式> |
这是利用test 命令进行条件测试表达式的方法。test 命令和<测试表达式> 之间至少有一个空格 |
语法2:[<测试表达式>] |
这是通过[] (单中括号)进行条件测试表达式的方法,和test 命令的用法相同,这是推荐使用的方法。[] 的边界和内容之间至少有一个空格 |
语法3:[[<测试表达式>]] |
这是通过[[]] (双中括号)进行条件测试表达式的方法,是比test 和[] 更新的语法格式。[[]] 的边界和内容之间至少有一个空格 |
语法4:((<测试表达式>)) |
这是通过(()) (双小括号)进行条件测试表达式的方法,一般用于if语句里。(()) (双小括号)两端不需要有空格。 |
上表中有几个注意事项需要说明一下:
- 语法1中的
test
命令和语法2中的[]
是等价的。语法3中的[[]]
为扩展的test
命令,语法4中的(())
常用于计算。建议以后使用相对友好的语法2,即中括号[]
的语法格式。 - 在
[[]]
(双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。 &&
、||
、>
、<
等操作符可以应用于[[]]
中,但不能应用于[]
中,在[]
中一般用-a
、-o
、-gt
(用于整数)、-lt
(用于整数)代替上述操作符。- 对于整数的关系运算,也可以使用Shell的算术运算符
(())
。
1.2. test条件测试的简单语法及示例
test条件测试的语法格式:test <测试表达式>
对于如下语句:test -f file && echo true || echo false
。该语句表示如果file文件存在,则输出true,否则(||)输出false。这里的&&是并且的意思。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的,命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
test命令测试表达式的逻辑也可以用上述表达式的一般逻辑(即仅有一个&&或||)来测试,示例如下:
另外,逻辑操作符&&
和||
的两端既可以有空格,也可以无空格,这就要看个人的编程习惯了。
范例:在test命令中使用-f选项(文件存在且为普通文件则表达式成立)测试文件:
范例:在test命令中使用-z选项(如果测试字符串的长度为0,则表达式成立)测试字符串:
提示:关于test测试表达式的更多知识可执行
man test
查看帮助,但大部分场景都会使用[]
的语法替代test
命令的语法。
结论:test命令测试的功能很强大,但是和[]
、[[]]
的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其它的语法,能读懂别人写的脚本就可以了。
1.3. [](中括号)条件测试语法及示例
[]
条件测试的语法格式为:[ <测试表达式> ]
注意:中括号内部的两端要有空格,
[]
和test
等价,即test
的所有判断选择都可以直接在[]
里使用。
对于如下语句:[ -f /tmp/theshu.txt ] && echo 1 || echo 0
。如果/tmp/theshu.txt
文件存在,则输出1,否则(||
)输出0.这里的&&
表示并且。[]
的应用同test
命令,若中括号里的命令执行成功(返回真),则执行&&
后面的命令,否则执行||
后面的命令。
[]
测试表达式的逻辑也可以用如下的语法来判断逻辑的表达式写法(test命令的用法也适合于此),即:
另外,逻辑操作符&&
和||
的两端可以有空格也可以无空格。
示例:利用[]
加-f选项(文件存在且为普通文件则表达式成立)测试文件:
提示:
[]
命令的选项和test
命令的选项是通用的,因此,使用[]
时的参数选项可以通过man test
命令获取帮助。
1.4. [[]] 条件测试语法及示例
[[]]
条件测试的语法格式为:[[ <测试表达式> ]]
。注意:双括号里的两端也要有空格。
对于如下语句:[[ -f /tmp/theshu.txt ]] && echo 1 || echo 0
。如果/tmp/theshu.txt
文件存在就输出1,否则(||)就输出0.这里的&&
表示并且。[[]]
的应用属于[]
和test
命令的扩展命令,功能更丰富也更复杂。如果双括号里的表达式成立(为真),则执行&&
后面的名利个,否则执行||
后面的命令。
[[]]
测试表达式的逻辑也可以使用如下的部分逻辑形式,即:
另外,逻辑操作符&&
和||
的两端可以有空格也可以无空格。
双中括号内部的两端要有空格,[[]]
里的测试判断选项,也可以通过man test
来获得,[[]]
表达式与[]
和test
用法的选项部分是相同的,其与[]
和test
测试表达式的区别在于,在[[]]
中可以使用通配符等进行模式匹配;并且&&
、||
、>
、<
等操作符可以用用[[]]
中,但不能应用于[]
中,在[]
中一般使用-a
、-o
、-gt
(用于整数)、-lt
(用于整数)等操作符代替上文提到的用于[[]]``中的符号。除了使用通配符功能之外,建议放弃这个双括号的写法,虽然它是较新的
test`命令的语法格式。
范例:[[]]
的使用示例:
有关test
、[]
、[[]]
这些操作符的用法,通过help test
或man test
查询即可得到帮助,完整的[]
、[[]]
用法可通过man bash
来获取。
2. 文件测试表达式
2.1. 文件测试表达式的用法
在书写文件测试表达式时,通常可以使用下表中的文件测试操作符:
常用文件测试操作符 | 说明 |
---|---|
-d 文件 ,d为directory |
文件存在且为目录则为真,即测试表达式成立 |
-f 文件 ,f为file |
文件存在且为普通文件则为真,即测试表达式成立 |
-e 文件 ,e为exist |
文件存在则为真,即测试表达式成立。注意区别与-f ,-e 不辨别是目录还是文件 |
-r 文件 ,r为read |
文件存在且可读则为真,即测试表达式成立 |
-s 文件 ,s为size |
文件存在且文件大小不为0则为真,即测试表达式成立 |
-w 文件 ,w为write |
文件存在且可写则为真,即测试表达式成立 |
-x 文件 ,x为executable |
文件存在且可执行则为真,即测试表达式成立 |
-L 文件 ,L为link |
文件存在且为链接文件则为真,即测试表达式成立 |
f1 -nt f2 ,nt为newer than |
文件f1 比文件f2 新则为真,即测试表达式成立。根据文件的修改时间来计算 |
f1 -ot f2 ,or为older than |
文件f1 比文件f2 旧则为真,即测试表达式成立。根据文件的修改时间来计算 |
上表列出的是企业里比较常用的操作符,这些操作符对于[[]]
、[]
、test
的测试表达式几乎是通用的,更多的操作符可通过man test
获得帮助。
2.2. 文件测试表达式举例
2.2.1. 普通文件测试表达式示例
普通文件(测试文件类型)
12345[root@theshu ~][root@theshu ~]-rw-r--r-- 1 root root 0 Feb 27 11:43 theshu[root@theshu ~]1目录文件(测试文件类型)
12345678910[root@theshu ~]# touch theshu[root@theshu ~]# mkdir test[root@theshu ~]# [ -f test ] && echo 1 || echo 00[root@theshu ~]# [ -e test ] && echo 1 || echo 01[root@theshu ~]# [ -d test ] && echo 1 || echo 01[root@theshu ~]# [ -d teshu ] && echo 1 || echo 00
2.2.2. 测试文件属性示例
- 范例:文件属性条件表达式测试实践:12345678910111213141516171819202122[root@theshu ~]-rw-r--r-- 1 root root 0 Feb 27 11:43 theshu[root@theshu ~]1[root@theshu ~]1[root@theshu ~]0[root@theshu ~][root@theshu ~]---------x 1 root root 0 Feb 27 11:43 theshu[root@theshu ~]1[root@theshu ~][root@theshu ~]1[root@theshu ~]echo test[root@theshu ~]1[root@theshu ~]test
提示:测试文件的读、写、执行等属性,不光是根据文件属性
rwx
的标识来判断,还要看当前执行测试的用户是否真的可以按照对应的权限操作该文件。
2.2.3. 测试Shell变量示例
首先定义
file1
和file2
两个变量,并分别赋予这两个变量对应的系统文件路径及文件名的值,如下:123[root@theshu ~]# file1=/etc/services ; file2=/etc/rc.local[root@theshu ~]# echo $file1 $file2/etc/services /etc/rc.local范例:对单个文件变量进行测试:
12345678[root@theshu ~]# [ -f "$file1" ] && echo 1 || echo 01 #<==文件存在且为普通文件,所以为真(1)[root@theshu ~]# [ -d "$file2" ] && echo 1 || echo 00 #<==是文件而不是目录,所以为假(0)[root@theshu ~]# [ -s "$file1" ] && echo 1 || echo 01 #<==文件存在且大小不为0,所有为真(1)[root@theshu ~]# [ -e "$file1" ] && echo 1 || echo 01 #<==文件存在,所以为真(1)范例:对单个目录或文件进行测试:
12345678[root@theshu ~]# [ -e /etc ] && echo 1 || echo 01[root@theshu ~]# [ -w /etc/services ] && echo 1 || echo 01[root@theshu ~]# su - theshu #<==切换到普通用户Last login: Tue Feb 27 10:35:07 CST 2018 from 221.199.162.130 on pts/0[theshu@theshu ~]$ [ -w /etc/services ] && echo 1 || echo 00 #<==文件不可写,所以返回0测试时变量的特殊写法级问题
用
[]
测试变量时,如果被测试的变量不加双引号,那么测试结果可能会是不正确的,示例如下:123456[root@theshu ~][root@theshu ~]1[root@theshu ~]0如果是文件实体路径,那么加引号与不加引号的结果是一样的:
1234[root@theshu ~]1[root@theshu ~]1范例:在生产环境下,系统NFS启动脚本(
/etc/init.d/nfs
)的条件测试:12345# Source networking configuration.[ -f /etc/sysconfig/network ] && . /etc/sysconfig/network# Check for and source configuration file otherwise set defaults[ -f /etc/sysconfig/nfs ] && . /etc/sysconfig/nfs
特别提示:系统脚本是我们学习编程的第一标杆,新手要多多参考脚本来学习,虽然有些脚本也不是特别规范。
- 范例:实现系统bind启动脚本named(bind DNS服务):1234[ -r /etc/sysconfig/network ] && . /etc/sysconfig/network#<==若文件存在且可读,则加载/etc/sysconfig/network[ -x /usr/sbin/$named ] || exit 5#<==如果/usr/sbin/$named不可执行,则退出
特别提示:前面所将的都是
[ -f /etc ] && echo 1 || echo 0
的用法,bind启动脚本[ -x /usr/sbin/$named ] || exit 5
的用法更值得注意,这里只用了一部分判断,结果却更简洁。
- 范例:写出简单高效的测试文件
- 在做测试判断时,不一定非得按照:
前面的操作成功了如何,否则如何
的方法来进行。直接做部分判断,有时看起来更简洁。例如:12[ -x theshu ] && echo 1[ -f /etc] || echo 0
- 在做测试判断时,不一定非得按照:
2.3. 特殊条件测试表达式案例
以下写法适用于所有的条件测试表达式,是工作中比较常用的替代if语句的方法。判断条件测试表达式的条件成立或不成立后,还需要继续执行多条命令语句的语法形式如下。
例如:当条件1成立时,同时执行命令1、命令2、命令3.不用if测试表达式的格式如下:
上面的判断相当于下面的if语句的效果:
上述的结构是当满足条件执行大括号里面的命令,如果是当条件不满足时才执行大括号里面的命令,那就要把&&
换成||
。
范例:当条件不成立时,执行大括号里的多条命令,这里要使用逻辑操作符||
:
如果把上述脚本写在一行里面,那么里面的每个命令都需要用分号结尾,示例如下:
3. 字符串测试表达式
3.1. 字符串测试操作符
字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。(NULL是指通过bash区分零长度字符串和空字符串)。
在书写测试表达式时,可以使用下表中的字符串测试操作符:
常用字符串测试操作符 | 说明 |
---|---|
-n "字符串" |
若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为no zero |
-z "字符串" |
若字符串的长度为0,则为真,即测试表达式成立,z可以理解为zero 的缩写 |
"串1"="串2" |
若字符串1等于字符串2,则为真,即测试表达式成立,可使用== 代替= |
"串1"!="串2" |
若字符串1不等于字符串2,则为真,即测试表达式成立,但不能用!== 代替!= |
以下是针对字符串测试操作符的提示:
- 对于字符串的测试,一定要将字符串加双引号之后再进行比较。如
[ -n "$myvar" ]
,特别是使用[]
的场景。 - 比较符号(例如
=
和!=
)的两端一定要有空格。 !=
和=
可用于比较两个字符串是否相同。
字符串比较时的两个错误用法:
字符串比较是若等号两端没有空格,则会导致判断出现逻辑错误,即使语法没问题,但是结果依然可能不对。示例如下:
1234[root@theshu ~]1[root@theshu ~]0字符串不加双引号,可能会导致判断上出现逻辑错误,即使语法没问题,但是结果依然可能不对。示例如下:
1234567[root@theshu ~]# var="" #<==将变量内容置空[root@theshu ~]# [ -n "$var" ] && echo 1 || echo 0 #<==有双引号0 #<==给变量加双引号,返回0,-n不为空时为真,因为变量内容为空吗,因此输出0是对的。[root@theshu ~]# [ -n $var ] && echo 1 || echo 0 #<==去掉双引号1 #<==同样的表达式,不加引号和加双引号后测试的结果相反,可见加双引号的重要性[root@theshu ~]# [ -z "$var" ] && echo 1 || echo 01 #<==如果字符串长度为0,则输出1,否则输出0
3.2. 字符串测试生产案例
范例:有关双引号和等号两端空格的生产系统标准:
4. 整数二元比较操作符
4.1. 整数二元比较操作符介绍
在书写测试表达式时,可以使用下面表中的整数二元比较操作符:
[] 和test 中使用 |
(()) 和[[]] 中使用 |
说明 |
---|---|---|
-eq |
== 或 = |
相等,equal |
-ne |
!= |
不想等,not equal |
-gt |
> |
大于,greater than |
-ge |
>= |
大于等于,greater equal |
-lt |
< |
小于 |
le |
<= |
小于等于,less equal |
以下是针对上述符号的特别说明:
=
和!=
也可以在[]
中做比较使用,但在[]
中使用包含>
和<
的符号时,需要用反斜杠转义,有时不转义虽然语法不会报错,但是结果可能会不对。- 也可以在
[[]]
中使用包含-gt
和-lt
的符号,但是不建议这样使用 - 比较符号两端也要有空格
范例:二元数字在[]
中使用<
、>
非标准符号的比较:
对于比较符号的应用,建议尽可能地按照上面表格中标记的方法来使用,以避免出现逻辑错误。
范例:二元数字在[]
中使用-gt
、-le
类符号的比较:
范例:二元数字配合不同种类的操作符在[[]]
中的比较:
提示:
[[]]
是扩展的test
命令,其语法更丰富也更复杂。对于实际工作中的常规比较,不建议使用[[]]
,会给Shell学习带来很多麻烦,除非是特殊的正则匹配等,在[]
无法使用的场景下才hi考虑使用[[]]
。
范例:二元数字在(())
中的比较:
有关[]
、[[]]
、(())
用法的小结:
- 整数加双引号的比较是对的
[[]]
中用类似-eq
等的写法是对的,[[]]
中用类似>
、<
的写法也可能不对,有可能会只比较第一位,逻辑结果不对[]
中用类似>
、<
的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=
、!=
正确比较(())
中不能使用类似-eq
等的写法,可以使用类似>
、<
的写法
提示:对于工作场景中的整数比较,推荐使用
[]
(类似-eq
的用法),当然使用(())
的写法也是可以的。(只需要养成一个习惯即可)
4.2. 整数变量测试实践示例
范例:通过[]
实现整数条件测试
提示:有关整数(要确认是整数,否则hi报错)大小的比较,推荐使用本例中的方法。
范例:利用[[]]
和(())
实现直接通过常规数学运算符进行比较:
有关整数(要确认是整数,否则会报错)的大小比较,(())
语法要优于[[]]
,但还是推荐优先使用[]
,次选是(())
,不推荐使用[[]]
。示例如下:
5. 逻辑操作符
5.1. 逻辑操作符介绍
在书写测试表达式时,可以使用下表中的逻辑操作符实现复杂的条件测试:
[] 和test 中使用 |
[[]] 和(()) 中使用 |
说明 |
---|---|---|
-a |
&& |
and,与,两端都为真,则结果为真 |
-o |
双竖线 |
or,或,两端有一个为真,则结果为真 |
! |
! |
not,非,两端相反,则结果为真 |
对于上述操作符,有如下提示:
- 逻辑操作符前后的表达式是否成立,一般用真假来表示
!
的中文意思是反
,即与一个逻辑值相反的逻辑值‘-a
的中文意思是与
(and
或&&
),前后两个逻辑值都为真
,综合返回值才为真
,反之为假
-o
的中文意思是或
(or
或||
),前后两个逻辑值只要有一个为真
,,返回值就为真
- 连接两含
[]
、test
或[[]]
的表达式可用&&
或||
逻辑操作符运算规则:
-a
和&&
的运算规则:只有逻辑操作符两端的表达式都成立时才为真;真(true)表示成立,对应的数字为1
;假(false)表示不成立,对应的数字为0
,这一点相当于如下表达式:1234[root@theshu ~]# [ -f /etc/hosts -a -f /etc/services ] && echo 1 || echo 01 #<==单中括号文件测试[root@theshu ~]# [[ -f /etc/hosts && -f /etc/services ]] && echo 1 || echo 01 #<==双中括号文件测试使用
-a
或&&
的总和表达式结果,相当于将两端表达式结果的对应数字(0或1)相乘:1234and结果1*0=0 假and结果0*1=0 假and结果1*1=1 真and结果0*0=0 假结论:
and(&&)
也称为与
,只有两端都是1时才为真,相当于取前后表达式的交集-o
或||
两端都是0
才为假,任何一端不为0就是真,这相当于将两边表达式结果的对应数字(0或1)相加,对应的表达式为:1234[root@theshu ~]# [ 5 -eq 6 -o 5 -gt 3 ] && echo 1 || echo 01[root@theshu ~]# ((5==6||5>3)) && echo 1 || echo 01-o
或||
的运算规则为:1234or 结果1+0=1 真or 结果1+1=2 真(非0即为真)or 结果0+1=1 真or 结果0+0=0 假结论:
or(||)
也称为或
,它的两端表达式的结果都是0时才为假,不为0就是真。相当于对前后表达式结果取并集。
5.2. 逻辑操作符实践示例
范例:[]
里的操作符错用&&
等逻辑运算符示例:
总结前面已讲过的内容:
-a
和-o
逻辑操作符号需要用于[]
中&&
和||
逻辑操作符号可用于[[]]
或(())
中,也可以在外部连接多个[]
- 注意,在
[]
和[[]]
的两端及比较符号的两端,必须要有空格,但是对于(())
不需要。 - 提示:
[]
中使用-a
或-o
更常见,[[]]
中使用&&
或||
不常见,使用&&
或||
连接两个[]
的多表达式判断也不常见。
范例:系统启动脚本中有关[[]]
的用法和与或非判断的使用案例
- 在操作系统中,
[[]]
的用法不是很多,并且大多数情况都用于与通配符匹配的场景。 这里不得不通过大海捞针的方法(遍历
/etc.init.d/
下的所有的脚本)来查找[[]]
的用法:123456[root@theshu ~]# for n in `ls /etc/init.d/*`;do egrep -wn "\[\[ " $n&&echo $n;done50: if [[ $route == *" via "* ]] ; then75: if ! [[ "$SYSLOGADDR" =~ $IPv4_regex ]] && ! [[ "$SYSLOGADDR" =~ $IPv6_regex ]]; then80: if [[ $? -eq 2 ]]; then84: if [[ $? -ne 0 ]]; then/etc/init.d/netconsole由上述遍历可见,
[[]]
的普通应用场景不多,但在[[]]
通配符匹配的场景下,其它测试表达式无法替代,因此,如果需要通配符或正则匹配就用[[]]
5.3. 逻辑操作符企业案例
范例:打印选择菜单,按照选项一键安装不同的Web服务。
示例菜单:
要求:
- 当用户输入1时,输出”start installing lamp”提示,然后执行/server/scripts/lamp.sh,输出”lamp is installed”,并退出脚本,此为工作中所用的lamp一键安装脚本。
- 当用户输入2时,输出”start installing lnmp”提示,然后执行/server/scripts/lnmp.sh,输出”lnmp is installed”,并退出脚本,此为工作中所用的lnmp一键安装脚本。
- 当输入3时,退出当前菜单及脚本。
- 当输入任何其他字符时,给予提示”Input error”后退出脚本。
- 对执行的脚本进行相关的条件判断,例如:脚本文件是否存在,是否可执行等的判断。
打印简单的所选菜单示例1:
下面是正式脚本的内容:
提示:这里关于判断用户的输入是否等于1、2或3的用法共给出了三种。请一定要重视。使用菜单时还可以用
select
语句,不过还是cat
的方法更常用,也更简单。
6. 测试表达式test、[]、[[]]、(()) 的区别总结
测试表达式的语法比较复杂且容易混淆,对于初学者,一定要个自己设定个知识边界。下表中列出了测试表达式[]
、[[]]
、(())
、test
的区别:
测试表达式符号 | [] |
test |
[[]] |
(()) |
---|---|---|---|---|
边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 |
逻辑操作符 | ! 、-a 、-o |
! 、-a 、-o |
! 、&& 、双竖线 |
! 、&& 、双竖线 |
整数比较操作符 | -eq 、-gt 、lt -ge 、-le |
-eq 、-gt 、lt 、-ge -le |
-eq 、-gt 、lt 、-ge 、-le 或 = 、> 、< 、>= 、<= |
= 、> 、< 、>= 、<= |
字符串比较操作符 | = 、== 、!= |
= 、== 、!= |
= 、== 、!= |
= 、== 、!= |
是否支持通配符匹配 | 不支持 | 不支持 | 支持 | 不支持 |
提示:普通的读者学习Shell编程主要是为了解决工作中的问题,因此无需掌握全部的语法,建议只用推荐的
[]
的用法,对其他的语法了解杰克,当有需要时,可以翻看资料或查阅bash文档man bash
,以及对应命令man test
的帮助。
OK